﻿/* 
 * Copyright (c) Intel Corporation
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * -- Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * -- Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * -- Neither the name of the Intel Corporation nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE INTEL OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ExtensionLoader;
using ExtensionLoader.Config;
using HttpServer;
using OpenSimDbLinq;
using CableBeachMessages;
using System.Collections;
using System.Text.RegularExpressions;
using System.Net;

namespace WorldServer.Extensions
{
    public class SimpleServicesGetter : IExtension<WorldServer>
    {
        WorldServer server;
        OpenSimDatabase userDatabase;
        int minimumLoginLevel = 0;

        static bool initialized = false;
        static Dictionary<Uri, Uri> webDavServiceCache;

        /* Getters setters */
        static public bool isInitialized { get { return initialized; } set { initialized = value; } }
        static public Dictionary<Uri, Uri> WebDavServiceCache { get { return webDavServiceCache; } set { webDavServiceCache = value; } }

        #region IExtension Members

        public bool Start(WorldServer server)
        {
            this.server = server;
            webDavServiceCache = new Dictionary<Uri, Uri>();
            isInitialized = true;

            #region Config Loading

            try
            {
                IConfig userdbConfig = server.ConfigFile.Configs["OpenSimUserDatabase"];
                IConfig loginConfig = server.ConfigFile.Configs["OpenSimLogin"];
                userDatabase = OpenSimUtils.LoadDatabaseFromConfig(userdbConfig);

                try
                {
                    // DEBUG:
                    //userDatabase.Log = Console.Out;

                    // Run a query to select the the first user in the database to confirm
                    // that database access is working
                    var creationDateQuery = from user in userDatabase.Users select user;
                    var result = creationDateQuery.SingleOrDefault();

                    if (result != null)
                        Logger.Info("[SimpleServicesGetter] Connected to user database");
                    else
                        Logger.Warn("[SimpleServicesGetter] Connected to an empty user database");
                }
                catch (Exception ex)
                {
                    Logger.Error("[SimpleServicesGetter] Failed to establish a database connection: " + ex.Message);
                    return false;
                }
            }
            catch (Exception)
            {
                Logger.Error("[SimpleServicesGetter] Failed to load [OpenSimDatabase] or [OpenSimLogin] section from " + WorldServer.CONFIG_FILE);
                return false;
            }

            #endregion

            // See handler doc what this does
            server.HttpServer.AddHandler("GET", null, @"^/services/webdav", GetWebDAVServiceForIdentity);

            return true;
        }

        public void Stop()
        {

        }

        #endregion

        /// <summary>
        /// Usage: After OpenSim auth/login -> "/services/webdav?type=normal&firstname=firstName&lastname=lastName"
        ///        After OpenID auth/login  -> "/services/webdav?type=openid&identity=http://identity.myprovider.com/"
        /// OpenID advantages: You can define your inventory URL and discover them during login. The inventory url is stored here to a cache,
        ///        if its not set then servers default webdav url is used.
        ///        
        /// Returns "CableBeach-Identity" : Uri, "CableBeach-WebDavInventory" : Uri in headers
        /// </summary>
        /// <param name="client"></param>
        /// <param name="request"></param>
        /// <param name="response"></param>
        void GetWebDAVServiceForIdentity(IHttpClientContext client, IHttpRequest request, IHttpResponse response)
        {
            String type = String.Empty, 
                   myIdentity = String.Empty, 
                   username = String.Empty,
                   query = request.Uri.Query;
            Uri serviceEndPoint, identityUri;

            if (query != "")
            {
                Hashtable queryTable = parseQueryString(request.Uri.Query.Substring(1,request.Uri.Query.Length-1));
                if (queryTable.ContainsKey("type"))
                    type = queryTable["type"].ToString().ToLower();

                // OpenSim
                if (type != String.Empty && type == "normal" && queryTable.ContainsKey("firstname") && queryTable.ContainsKey("lastname"))
                {
                    String firstName = queryTable["firstname"].ToString();
                    String lastName = queryTable["lastname"].ToString();

                    // Look up these credentials in the database
                    Users dbUser = userDatabase.Users.SingleOrDefault(user => user.UserName == firstName && user.LastName == lastName);
                    
                    if (dbUser != null)
                    {
                        // Check if this avatar has a high enough login level to login right now
                        if (dbUser.GodLevel >= minimumLoginLevel)
                        {
                            // Create identityUri from first/lastname
                            identityUri = new Uri(server.HttpUri, "/users/" + firstName + "." + lastName);
                            // Get from local cache
                            if (webDavServiceCache.TryGetValue(identityUri, out serviceEndPoint))
                            {
                                response.AddHeader("CableBeach-Identity", identityUri.ToString());
                                response.AddHeader("CableBeach-WebDavInventory", serviceEndPoint.ToString());
                                response.Status = HttpStatusCode.OK;
                                Logger.Debug("[SimpleServicesGetter] Returning webdav service endpoint from local cache for identity " + identityUri.ToString());
                            }
                            // Get from services
                            else
                            {
                                ServiceCollection services = new ServiceCollection();
                                server.ServiceProvider.GetServices(identityUri, true, ref services);

                                foreach (KeyValuePair<Uri, Service> kvp in services)
                                {
                                    foreach (KeyValuePair<Uri, Uri> capability in kvp.Value.Capabilities)
                                    {
                                        if (capability.Key.ToString().Equals(CableBeachServices.FILESYSTEM_GET_WEBDAV_ROOT))
                                        {
                                            Logger.Debug("[SimpleServicesGetter] Returning webdav service endpoint from required services for identity " + identityUri.ToString());
                                            response.AddHeader("CableBeach-Identity", identityUri.ToString());
                                            response.AddHeader("CableBeach-WebDavInventory", capability.Value.ToString());
                                            response.Status = HttpStatusCode.OK;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    else
                    {
                        Logger.Debug("[SimpleServicesGetter] Could not find user from OpenSim db, invalid  firstname and/or lastname in query");
                        response.AddToBody("Could not find user from OpenSim db, check your firstname and lastname in query");
                        response.Status = HttpStatusCode.NotFound;
                    }
                } 
                // OpenID
                else if ( type == "openid" && queryTable.ContainsKey("identity") )
                {
                    myIdentity = queryTable["identity"].ToString();
                    identityUri = new Uri(myIdentity);
                    // Get from local cache
                    if (webDavServiceCache.TryGetValue(identityUri, out serviceEndPoint))
                    {
                        response.AddHeader("CableBeach-Identity", identityUri.ToString());
                        response.AddHeader("CableBeach-WebDavInventory", serviceEndPoint.ToString());
                        response.Status = HttpStatusCode.OK;
                        Logger.Debug("[SimpleServicesGetter] Returning webdav service endpoint from local cache for identity " + identityUri.ToString());
                    }
                    else
                    {
                        Logger.Debug("[SimpleServicesGetter] Could not find OpenID identity " + identityUri + " from local cache");
                        response.AddToBody("Could not find OpenID identity " + identityUri + " from local cache, do login first");
                        response.Status = HttpStatusCode.NotFound;
                    }
                }
                else
                {
                    Logger.Debug("[SimpleServicesGetter] Invalid query values while getting webdav service endpoint");
                    response.AddToBody("Invalid query values");
                    response.Status = HttpStatusCode.NotFound;
                }
            }
            else
            {
                Logger.Debug("[SimpleServicesGetter] Invalid query values while getting webdav service endpoint");
                response.AddToBody("Invalid query values");
                response.Status = HttpStatusCode.NotFound;
            }
        }

        /// <summary>
        /// Split the Url query string with & and make it a key, value hashtable
        /// </summary>
        /// <param name="qstring"></param>
        /// <returns></returns>
        private Hashtable parseQueryString(string qstring)
        {
            qstring = qstring + "&";
            Hashtable outc = new Hashtable();
            Regex r = new Regex(@"(?<name>[^=&]+)=(?<value>[^&]+)&", RegexOptions.IgnoreCase | RegexOptions.Compiled);
            IEnumerator _enum = r.Matches(qstring).GetEnumerator();

            while (_enum.MoveNext() && _enum.Current != null)
            {
                outc.Add(((Match)_enum.Current).Result("${name}"),
                         ((Match)_enum.Current).Result("${value}"));
            }
            return outc;
        }
    }
}
